﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SceneLib;
using Tao.OpenGl;
using SplineLib;

namespace OpenGL
{
    class Road
    {
        private SceneMaterial metalMaterial;
        private SceneModel woodModel;
        private SplineInfo spline;
        private float sample;

        public Road(SceneMaterial material, SceneModel model, SplineInfo theSpline, float theSample)
        {
            metalMaterial = material;
            woodModel = model;
            sample = theSample;
            spline = theSpline;
        }

        public void Draw()
        {
            Gl.glMaterialfv(Gl.GL_FRONT_AND_BACK, Gl.GL_SPECULAR,
                            MathUtils.GetVector3Array(metalMaterial.Specular));
            Gl.glMaterialf(Gl.GL_FRONT_AND_BACK, Gl.GL_SHININESS,
                           metalMaterial.Shininess);
            Gl.glColor3fv(MathUtils.GetVector3Array(metalMaterial.Diffuse));

            SetTextures(metalMaterial);

            DrawLine(-0.18f, 0.05f);
            DrawLine(0.18f, 0.05f);
            DrawWood();
        }

        private void DrawWood()
        {
            float t;
            Vector normal, binormal;
            Vector tangent;

            t = 0.0f;
            tangent = spline.SplineFunctionPrime(t);
            tangent.Normalize3();
            normal = Vector.Cross3(Vector.Cross3(spline.SplineFunctionPrime(t), spline.SplineFunctionDoublePrime(t)),
                                   spline.SplineFunctionPrime(t));
            normal.Normalize3();
            binormal = Vector.Cross3(tangent, normal);
            binormal.Normalize3();
            t += sample;


            Gl.glMaterialfv(Gl.GL_FRONT_AND_BACK, Gl.GL_SPECULAR,
                            MathUtils.GetVector3Array(woodModel.Triangles[0].Materials[0].Specular));
            Gl.glMaterialf(Gl.GL_FRONT_AND_BACK, Gl.GL_SHININESS,
                           woodModel.Triangles[0].Materials[0].Shininess);
            Gl.glColor3fv(MathUtils.GetVector3Array(woodModel.Triangles[0].Materials[0].Diffuse));
            SetTextures(woodModel.Triangles[0].Materials[0]);

            while (t < spline.Points.Count)
            {
                Vector point = spline.SplineFunction(t);
                Matrix m = Matrix.GetRotationMatrix(binormal, normal, tangent);

                tangent = spline.SplineFunctionPrime(t);
                tangent.Normalize3();
                normal = Vector.Cross3(binormal, tangent);
                normal.Normalize3();
                binormal = Vector.Cross3(tangent, normal);
                binormal.Normalize3();

                Gl.glPushMatrix();

                Gl.glTranslatef(point.x, point.y, point.z);
                Gl.glMultMatrixf(MathUtils.GetMatrixArray(m));
                Gl.glTranslatef(woodModel.Position.x, woodModel.Position.y, woodModel.Position.z);
                Gl.glRotatef(woodModel.Rotation.x, 1.0f, 0.0f, 0.0f);
                Gl.glRotatef(woodModel.Rotation.y, 0.0f, 1.0f, 0.0f);
                Gl.glRotatef(woodModel.Rotation.z, 0.0f, 0.0f, 1.0f);
                Gl.glScalef(woodModel.Scale.x * 0.3f, woodModel.Scale.y * 0.3f, woodModel.Scale.z * 0.3f);

                Gl.glEnableClientState(Gl.GL_VERTEX_ARRAY);
                Gl.glEnableClientState(Gl.GL_TEXTURE_COORD_ARRAY);
                Gl.glEnableClientState(Gl.GL_NORMAL_ARRAY);

                Gl.glNormalPointer(Gl.GL_FLOAT, 0, woodModel.NormalsArray);
                Gl.glVertexPointer(3, Gl.GL_FLOAT, 0, woodModel.VertexArray);
                Gl.glTexCoordPointer(2, Gl.GL_FLOAT, 0, woodModel.TexArray);

                short[] indexes = woodModel.IndexArray;
                Gl.glDrawElements(Gl.GL_TRIANGLES, indexes.Length,
                                  Gl.GL_UNSIGNED_SHORT, indexes);

                Gl.glDisableClientState(Gl.GL_TEXTURE_COORD_ARRAY);
                Gl.glDisableClientState(Gl.GL_VERTEX_ARRAY);
                Gl.glDisableClientState(Gl.GL_NORMAL_ARRAY);
                Gl.glPopMatrix();
                t += sample;
            }
        }

        private void DrawLine(float factor, float width)
        {
            float t;
            Vector normal, binormal;
            Vector tangent;

            Gl.glBegin(Gl.GL_QUAD_STRIP);
            t = 0.0f;
            tangent = spline.SplineFunctionPrime(t);
            tangent.Normalize3();
            normal = Vector.Cross3(Vector.Cross3(spline.SplineFunctionPrime(t), spline.SplineFunctionDoublePrime(t)),
                                   spline.SplineFunctionPrime(t));
            normal.Normalize3();
            binormal = Vector.Cross3(tangent, normal);
            binormal.Normalize3();
            t += sample;

            while (t < spline.Points.Count)
            {
                Vector point = spline.SplineFunction(t);
                Vector v1, v2;

                tangent = spline.SplineFunctionPrime(t);
                tangent.Normalize3();
                normal = Vector.Cross3(binormal, tangent);
                normal.Normalize3();
                binormal = Vector.Cross3(tangent, normal);
                binormal.Normalize3();

                if (factor > 0)
                {
                    v1 = point + (factor + width) * binormal;
                    v2 = point + factor * binormal;
                }
                else
                {
                    v1 = point + factor * binormal;
                    v2 = point + (factor - width) * binormal;
                }

                Gl.glNormal3f(-normal.x, -normal.y, -normal.z);
                Gl.glVertex3f(v1.x, v1.y, v1.z);
                Gl.glNormal3f(-normal.x, -normal.y, -normal.z);
                Gl.glVertex3f(v2.x, v2.y, v2.z);
                t += sample;
            }

            Gl.glEnd();
        }

        private void SetTextures(SceneMaterial sceneMat)
        {
            if (sceneMat.TextureImage != null)
            {

                Gl.glEnable(Gl.GL_TEXTURE_2D);

                sceneMat.CreatePointer();

                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR);
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR);
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_REPEAT);
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_REPEAT);

                //Seteo de la textura usada
                Gl.glTexImage2D(Gl.GL_TEXTURE_2D,
                                0,
                                Gl.GL_RGBA,
                                sceneMat.TextureImage.Width,
                                sceneMat.TextureImage.Height,
                                0,
                                Gl.GL_BGR,
                                Gl.GL_UNSIGNED_BYTE,
                                sceneMat.PtrBitmap);
            }
            else
            {
                Gl.glDisable(Gl.GL_TEXTURE_2D);
            }
        }
    }
}
